{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a8609f76",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torchvision.datasets as ds\n",
    "import torchvision.transforms as transforms\n",
    "from torch.autograd import Variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "971199f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "MNINST数据集中图片是28*28大小，使用LSTM结构把每一行当作一个输入，共有28个输入序列，每次输入大小为28，相当于把以前输入的行记忆下来\n",
    "\"\"\"\n",
    "sequence_length = 28\n",
    "input_size = 28\n",
    "hidden_size = 128\n",
    "num_layers = 2\n",
    "num_classes = 10\n",
    "batch_size = 100\n",
    "num_epochs = 2\n",
    "learning_rate = 0.003"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "81eb2256",
   "metadata": {},
   "outputs": [],
   "source": [
    "data_tf = transforms.Compose([transforms.ToTensor()])\n",
    "train_dataset = ds.MNIST('./data',train=True,transform=data_tf )\n",
    "test_dataset = ds.MNIST('./data',train=False,transform=data_tf )\n",
    "\n",
    "train_loader = torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)\n",
    "test_loader = torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f67d494e",
   "metadata": {},
   "outputs": [],
   "source": [
    "class BiRNN(nn.Module):\n",
    "    def __init__(self,input_size,hidden_size,num_layers,num_classes):\n",
    "        super(BiRNN,self).__init__()\n",
    "        self.hidden_size = hidden_size\n",
    "        self.num_layers = num_layers\n",
    "        self.lstm = nn.LSTM(input_size,hidden_size,num_layers,batch_first=True,bidirectional=True)  #bidirectional双向\n",
    "        self.fc = nn.Linear(hidden_size*2,num_classes)\n",
    "    \n",
    "    def forward(self,x):\n",
    "        #初始状态\n",
    "        #h0为隐藏状态\n",
    "        #c0为细胞状态\n",
    "        h0 = Variable(torch.zeros(self.num_layers*2,x.size(0),self.hidden_size)).cuda()\n",
    "        c0 = Variable(torch.zeros(self.num_layers*2,x.size(0),self.hidden_size)).cuda()\n",
    "        \n",
    "        #正向传播\n",
    "        \"\"\"\n",
    "        out（一个三维张量，其形状通常为[batch_size, seq_len, hidden_size*2]，其中hidden_size*2是因为使用了双向LSTM）和\n",
    "        最终状态（在这里我们没有使用最终状态，所以使用_来忽略它）\n",
    "        \"\"\"\n",
    "        out,_ = self.lstm(x,(h0,c0))\n",
    "        \n",
    "        #解码最后一个时间步长的隐藏状态\n",
    "        \"\"\"\n",
    "        out[:,-1:]：这是一个切片操作，用于从out这个三维张量中提取每个序列的最后一个时间步的输出。\n",
    "        :表示在第一个维度（batch维度）上选择所有元素，-1表示在第二个维度（序列长度维度）上选择最后一个元素，\n",
    "        而:在第三个维度（特征维度）上选择所有元素。因此，这个切片操作将返回一个形状为[batch_size, hidden_size*2]的二维张量。\n",
    "        \"\"\"A\n",
    "        \n",
    "        \"\"\"\n",
    "        选择每个序列的最后一个时间步的隐藏状态作为输出而不是使用LSTM的最终状态作为输出，主要有以下原因：\n",
    "\n",
    "        序列分类任务：在很多序列分类任务中，我们关心的是整个序列的信息，而不是序列中某个特定位置的信息。\n",
    "        LSTM的最终状态通常包含了序列从开始到结束的所有信息，但是这些信息可能会因为网络架构、序列长度等因素而变得模糊或难以解释。\n",
    "而最后一个时间步的隐藏状态，尤其是双向LSTM的最后一个时间步的隐藏状态，因为它同时包含了正向和反向的信息，所以通常能够很好地代表整个序列的信息。\n",
    "        \"\"\"\n",
    "        out = self.fc(out[:,-1:])\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2e1f9671",
   "metadata": {},
   "outputs": [],
   "source": [
    "rnn = BiRNN(input_size,hidden_size,num_layers,num_classes)\n",
    "rnn.cuda()\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.Adam(rnn.parameters(),lr=learning_rate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "7d2254ef",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch[1/2],Step[100/600],loss:0.6249\n",
      "Epoch[1/2],Step[200/600],loss:0.3635\n",
      "Epoch[1/2],Step[300/600],loss:0.3065\n",
      "Epoch[1/2],Step[400/600],loss:0.2003\n",
      "Epoch[1/2],Step[500/600],loss:0.1000\n",
      "Epoch[1/2],Step[600/600],loss:0.1452\n",
      "Epoch[2/2],Step[100/600],loss:0.1249\n",
      "Epoch[2/2],Step[200/600],loss:0.0634\n",
      "Epoch[2/2],Step[300/600],loss:0.0505\n",
      "Epoch[2/2],Step[400/600],loss:0.1421\n",
      "Epoch[2/2],Step[500/600],loss:0.0690\n",
      "Epoch[2/2],Step[600/600],loss:0.0658\n"
     ]
    }
   ],
   "source": [
    "for epoch in range(num_epochs):\n",
    "    for i,(images,labels) in enumerate(train_loader):\n",
    "        #这里labels是batch_size个数字,images是batch_size张1*28*28的照片\n",
    "        #把images变成batch_size*sequence_length*input_size大小的向量（100*1*28*28变成100*28*28）\n",
    "        images = Variable(images.view(-1,sequence_length,input_size)).cuda()\n",
    "        labels = Variable(labels).cuda()\n",
    "        \n",
    "        optimizer.zero_grad()\n",
    "        outputs = rnn(images)\n",
    "        #100*1*10变成100*10\n",
    "        outputs = outputs.view(-1,num_classes)\n",
    "        loss = criterion(outputs, labels)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        if(i+1) % 100 == 0:\n",
    "            print('Epoch[%d/%d],Step[%d/%d],loss:%.4f'%(epoch+1,num_epochs,i+1,len(train_dataset)//batch_size,loss.item()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "7aa0c79d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test Accuracy of the model on the 10000 test images:98\n"
     ]
    }
   ],
   "source": [
    "correct = 0\n",
    "total = 0\n",
    "for images,labels in test_loader:\n",
    "    images = Variable(images.view(-1,sequence_length,input_size)).cuda()\n",
    "    labels = Variable(labels).cuda()\n",
    "    outputs = rnn(images)\n",
    "    outputs = outputs.view(-1,num_classes)\n",
    "    _, predicted = torch.max(outputs.data,1)\n",
    "    total += labels.size(0)\n",
    "    correct += (predicted == labels).sum()\n",
    "\n",
    "print(\"Test Accuracy of the model on the 10000 test images:%d%%\"%(100*correct/total))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b17f159",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:pytorch2] *",
   "language": "python",
   "name": "conda-env-pytorch2-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
